package com.server;

import com.data.Universe;
import com.data.UniverseManager;
import com.data.Wheel;
import com.data.WheelManager;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ArtNetProcessor {

    private final static int rowCount = 3;
    private final static int columnCount = 6;
    private final static int ArtDmxOpcode = 0x5000;
    private final static int universeRowSize = 13;
    public static final int DATA_OFFSET = 18;

    private byte[] magic;
    private DatagramSocket sendSocket;

    public ArtNetProcessor() {
        try {
            magic = "Art-Net ".getBytes("Cp1251");
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        magic[7] = 0;
        try {
            sendSocket = new DatagramSocket();
        } catch (SocketException ex) {
            ex.printStackTrace();
        }
    }

    public void sendPicture(byte[] data) {
        int index = 0;

        for (int j = 0; j < rowCount; j++) {
            for (int i = 0; i < columnCount; i++, index++) {
                Universe universe = UniverseManager.getInstance().getUniverse(index);
                sendUniverse(i, j, universe, data);
            }
        }
    }

    private static int getFullDmxAddress(Universe universe) {
        return universe.getSubNet() * 16 + universe.getIndex();
    }

    private byte[] getDMXArray(Universe universe) {
        int dmxAddress = getFullDmxAddress(universe);
        byte[] artDmxPacket = new byte[18 + 1 + 512];//art-net-header-size+512 data
        System.arraycopy(magic, 0, artDmxPacket, 0, magic.length);
        artDmxPacket[8] = (byte) ArtDmxOpcode;//opcode
        artDmxPacket[9] = (byte) (ArtDmxOpcode >> 8);//opcode low
        artDmxPacket[10] = (byte) 0;//protocol hi
        artDmxPacket[11] = (byte) 14;//protocol low
        artDmxPacket[12] = (byte) 0;//sequence
        artDmxPacket[13] = (byte) 0;//physical
        artDmxPacket[14] = (byte) dmxAddress;
        artDmxPacket[15] = (byte) (dmxAddress >> 8);
        artDmxPacket[16] = (byte) (512 >> 8);//data size
        artDmxPacket[17] = (byte) (512);//data size
        return artDmxPacket;

    }

    public void sendPixelUniverse(int indexFrom, int indexTo, Universe universe, byte r, byte g, byte b) {
        byte[] artDmxPacket = getDMXArray(universe);

        for (int i = DATA_OFFSET + indexFrom; i <= DATA_OFFSET + indexTo * 3; i += 3) {
            artDmxPacket[i] = r;
            artDmxPacket[i + 1] = g;
            artDmxPacket[i + 2] = b;
        }

        sendToSocket(artDmxPacket, universe);
    }

    private void sendUniverse(int column, int row, Universe universe, byte[] data) {
        byte[] artDmxPacket = getDMXArray(universe);

        ByteMatrix bm = new ByteMatrix(data, column, row, 78 * 3, 39 * 3, 13 * 3, 13);
        copyPictureDataToUniverse(artDmxPacket, bm, universe);

        processWheels(artDmxPacket, universe);

        if (universe.getPosition() == 13) {//hack to avoitd broken pixels
            removeBrokenPixels(artDmxPacket);
        }

        sendToSocket(artDmxPacket, universe);
     }

    private void sendToSocket(byte[]artDmxPacket, Universe universe)
    {
        DatagramPacket packet = new DatagramPacket(artDmxPacket, artDmxPacket.length);
        packet.setPort(universe.getPort());
        packet.setAddress(universe.getInetAddress());

        try {
            sendSocket.send(packet);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }


    public static void removeBrokenPixels(byte[] arr) {

        List<Byte> list = new LinkedList<Byte>();
        List<Byte> res = new ArrayList<>();
        for (Byte v : arr) {
            list.add(v);
        }

        res.addAll(list.subList(0, DATA_OFFSET + 55 * 3));
        res.addAll(list.subList(DATA_OFFSET + 56 * 3, DATA_OFFSET + 57 * 3));
        res.addAll(list.subList(DATA_OFFSET + 59 * 3, DATA_OFFSET + 61 * 3));
        res.addAll(list.subList(DATA_OFFSET + 62 * 3, DATA_OFFSET + 63 * 3));
        res.addAll(list.subList(DATA_OFFSET + 64 * 3, DATA_OFFSET + 142 * 3));
        res.addAll(list.subList(DATA_OFFSET + 143 * 3, DATA_OFFSET + 148 * 3));


        int i = 0;
        for (Byte v : res) {
            arr[i++] = v;
        }
        for (; i < 512; i++) {
            arr[i] = 0;
        }
    }

    public static void main(String[] a) {
        byte[] source = new byte[512];
        for (int i = 0; i < DATA_OFFSET; i++) {
            source[i] = (byte) i;
        }
        for (int i = 0; i < 512 - DATA_OFFSET; i++) {
            source[i + DATA_OFFSET] = (byte) ((byte) i / 3);
        }

        removeBrokenPixels(source);

    }

    private static void processWheels(byte[] packet, Universe universe) {
        // don't send for position 13
        if (universe.getPosition() == 13) {
            return;
        }

        Wheel wheel = universe.getPosition() == 7 ?
                WheelManager.getInstace().getWheelsForUniverse(UniverseManager.getInstance().getUniverse(13)) :
                WheelManager.getInstace().getWheelsForUniverse(universe);

        if (wheel == null) {
            return;
        }

        int wheelOffset = universe.getWheelIndexOffset();
        int speedl = 0;
        int speedr = 0;
        int s = wheel.getActualSpeed();
        if (s != 0) {
            if (s < 0) {
                speedl = Math.abs(s);
            } else {
                speedr = Math.abs(s);
            }
        }

        packet[DATA_OFFSET + wheelOffset + 0] = (byte) speedl;
        packet[DATA_OFFSET + wheelOffset + 1] = (byte) speedr;
        packet[DATA_OFFSET + wheelOffset + 2] = (byte) 255;
    }


    private static void copyPictureDataToUniverse(byte[] packet, ByteMatrix bm, Universe universe) {

        int sample = 0;
        int index = 0;
        int[] mask = universe.getPixelMapping();

        for (int j = 0; j < universeRowSize; j++) {
            for (int k = 0; k < universeRowSize; k++, index++) {
                int mappingvalue = mask[index];
                if (mappingvalue > -1) {
                    int value = bm.getValue(mappingvalue * 3 + 0);
                    //System.out.println("value=" + mappingvalue);

                    packet[DATA_OFFSET + sample + 0] = (byte) value;//picture[mappingvalue * 3 + 0];
                    packet[DATA_OFFSET + sample + 1] = bm.getValue(mappingvalue * 3 + 1);//picture[mappingvalue * 3 + 1];
                    packet[DATA_OFFSET + sample + 2] = bm.getValue(mappingvalue * 3 + 2);//picture[mappingvalue * 3 + 2];
                    sample += 3;
                } else {
                    //System.out.println("x=" + k + " y=" + j);
                    int x = 0;
                    continue;
                }
            }
        }
    }
}
